﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Web.UI;
using System.ComponentModel;
using System.Collections;
using System.Text;
using System.IO;
using System.Globalization;

namespace Succell.Dnn.JQuery
{
    /// <summary>
    /// A helper class for serializing jQuery UI widget settings.
    /// </summary>
    public static class SettingSerializer
    {
        private static void FetchSettings(object obj, IDictionary<string, object> settings)
        {
            if (null == obj)
                return;
            Type objType = obj.GetType();
            var members = objType.GetFields(BindingFlags.GetField | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
                .Cast<MemberInfo>()
                .Concat(
                objType.GetProperties(BindingFlags.GetProperty | BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)
                    .Cast<MemberInfo>());

            foreach (MemberInfo info in members)
            {
                SettingAttribute settingAttribute = (SettingAttribute)info.GetCustomAttributes(typeof(SettingAttribute), true).SingleOrDefault();
                if (null != settingAttribute)
                {
                    string name = info.Name;
                    if (!String.IsNullOrEmpty(settingAttribute.SettingName))
                    {
                        name = settingAttribute.SettingName;
                    }
                    object value;
                    FieldInfo fi = info as FieldInfo;
                    if (null != fi)
                    {
                        value = fi.GetValue(obj);
                    }
                    else
                    {
                        PropertyInfo pi = (PropertyInfo)info;
                        value = pi.GetValue(obj, null);
                    }
                    object defaultValue = null;
                    DefaultValueAttribute defaultValueAttribute =
                        (DefaultValueAttribute)
                        info.GetCustomAttributes(typeof(DefaultValueAttribute), true).SingleOrDefault();
                    if (null != defaultValueAttribute)
                    {
                        defaultValue = defaultValueAttribute.Value;
                    }
                    if ((null == defaultValue && null == value) || (null != defaultValue && defaultValue.Equals(value)))
                        continue;

                    if (null == value)
                    {
                        settings[name] = null;
                        continue;
                    }
                    if (value is ClientEventHandler)
                    {
                        ClientEventHandler h = (ClientEventHandler)value;
                        if ((null == defaultValue && String.IsNullOrEmpty(h.FunctionBody)) || (null != defaultValue && defaultValue.Equals(h.FunctionBody)))
                            continue;
                    }
                    Type valueType = value.GetType();
                    if (valueType.IsEnum)
                    {
                        string enumValueName = Enum.GetName(valueType, value);
                        FieldInfo enumField = valueType.GetField(enumValueName);
                        SerializeAsAttribute serializeAsAttribute = enumField.GetCustomAttributes(typeof(SerializeAsAttribute), false).Cast<SerializeAsAttribute>().SingleOrDefault();
                        settings[name] = null == serializeAsAttribute ? enumValueName : serializeAsAttribute.Value;
                        continue;
                    }
                    if (null != valueType.GetInterface(typeof(ISettingSerializable).FullName))
                    {
                        if (((ISettingSerializable)value).IncludeSetting)
                        {
                            settings[name] = value;
                        }
                    }
                    if (null != valueType.GetInterface(typeof (IDictionary).FullName))
                    {
                        IDictionary dictionary = (IDictionary) value;
                        EmptyValueSerializationMode emptyMode = settingAttribute.EmptyValueSerializationMode;
                        if (emptyMode == EmptyValueSerializationMode.Omit && dictionary.Count <= 0)
                        {
                            continue;
                        }
                        DictionarySerializationMode mode = settingAttribute.DictionarySerializationMode;
                        switch (mode)
                        {
                            case DictionarySerializationMode.Normal:
                                settings[name] = dictionary;
                                break;

                            case DictionarySerializationMode.KeysOnly:
                                settings[name] = dictionary.Keys;
                                break;

                            case DictionarySerializationMode.ValuesOnly:
                                settings[name] = dictionary.Values;
                                break;
                        }
                        continue;
                    }
                    if (null != valueType.GetInterface(typeof (IList).FullName))
                    {
                        IList list = (IList) value;
                        EmptyValueSerializationMode emptyMode = settingAttribute.EmptyValueSerializationMode;
                        if (emptyMode == EmptyValueSerializationMode.Omit && list.Count <= 0)
                        {
                            continue;
                        }
                        settings[name] = list;
                        continue;
                    }
                    if (value is string)
                    {
                        string s = (string) value;
                        if ((settingAttribute.StringValueOptions & StringValueOptions.CanContainBools) != 0)
                        {
                            if (s.Equals("true"))
                            {
                                settings[name] = true;
                                continue;
                            }
                            if (s.Equals("false"))
                            {
                                settings[name] = false;
                                continue;
                            }
                        }
                    }
                    if (value is DateTime)
                    {
                        DateTime dateTime = (DateTime) value;
                        if (settingAttribute.DateTimeSerializationMode == DateTimeSerializationMode.DateOnly)
                        {
                            settings[name] = dateTime.Date;
                            continue;
                        }
                        settings[name] = dateTime;
                        continue;
                    }
                    settings[name] = value;
                }
                else
                {
                    SettingGroupAttribute settingGroupAttribute = (SettingGroupAttribute)info.GetCustomAttributes(typeof(SettingGroupAttribute), true).SingleOrDefault();
                    if (null != settingGroupAttribute)
                    {
                        object value;
                        FieldInfo fi = info as FieldInfo;
                        if (null != fi)
                        {
                            value = fi.GetValue(obj);
                        }
                        else
                        {
                            PropertyInfo pi = (PropertyInfo)info;
                            value = pi.GetValue(obj, null);
                        }
                        FetchSettings(value, settings);
                    }
                }
            }
        }

        /// <summary>
        /// Parses a control for fields and properties marked with the
        /// <see cref="SettingAttribute"/> and returns a Dictionary containing the
        /// found settings and their values.
        /// </summary>
        /// <param name="control">The control to search for settings in.</param>
        /// <returns>A Dictionary containing the settings and their values.</returns>
        public static Dictionary<string, object> FetchSettings(Control control)
        {
            if (null == control)
                throw new ArgumentNullException("control");

            Dictionary<string, object> result = new Dictionary<string, object>();

            FetchSettings(control, result);

            return result;
        }

        private static void Serialize(TextWriter writer, object value)
        {
            if (null == value)
            {
                writer.Write("null");
            }
            if (value is ISettingSerializable)
            {
                writer.Write(((ISettingSerializable) value).ToJson());
            }
            else if (value is ClientEventHandler)
            {
                writer.Write(value.ToString());
            }
            else if (value is IDictionary)
            {
                writer.Write('{');
                Serialize(writer, (IDictionary)value);
                writer.Write('}');
            }
            else if (value is ICollection)
            {
                writer.Write('[');
                Serialize(writer, (ICollection)value);
                writer.Write(']');
            }
            else if (value is bool)
            {
                writer.Write((bool) value ? "true" : "false");
            }
            else if (value is byte
                || value is short
                || value is int
                || value is long
                || value is double
                || value is float)
            {
                writer.Write(value);
            }
            else if (value is string)
            {
                string s = (string) value;
                //if (s.StartsWith("function", StringComparison.Ordinal))
                //{
                //    writer.Write(s);
                //}
                //else
                //{
                    writer.Write('\'');
                    writer.Write(s);
                    writer.Write('\'');
                //}
            }
            else if (value is DateTime)
            {
                DateTime dateTime = (DateTime)value;
                if (dateTime.Hour > 0 || dateTime.Minute > 0 || dateTime.Second > 0 || dateTime.Millisecond > 0)
                {
                    writer.Write("new Date({0}, {1}, {2}, {3}, {4}, {5})",
                        dateTime.Year, dateTime.Month - 1, dateTime.Day,
                        dateTime.Hour, dateTime.Minute, dateTime.Second);
                }
                else
                {
                    writer.Write("new Date({0}, {1}, {2})",
                        dateTime.Year, dateTime.Month - 1, dateTime.Day);
                }
            }
            else
                throw new ArgumentException("value's type is not supported.", "value");
        }

        private static void Serialize(TextWriter writer, ICollection collection)
        {
            bool first = true;
            foreach (object obj in collection)
            {
                if (!first)
                    writer.Write(",");
                first = false;
                Serialize(writer, obj);
            }
        }

        private static void Serialize(TextWriter writer, IDictionary settings)
        {
            bool first = true;
            foreach (DictionaryEntry entry in settings)
            {
                if (!first)
                    writer.Write(',');
                first = false;
                string key = entry.Key as string;
                if (null == key)
                    throw new ArgumentException("Dictionary keys must be strings for json serialization.", "settings");

                writer.Write(entry.Key);
                writer.Write(':');
                Serialize(writer, entry.Value);
            }
        }

        /// <summary>
        /// Serializes a settings Dictionary into Json.
        /// </summary>
        /// <param name="settings">The settings Dictionary to serialize.</param>
        /// <returns>The Json representation of the settings Dictionary.</returns>
        public static string Serialize(Dictionary<string, object> settings)
        {
            StringBuilder sb = new StringBuilder();
            if (settings.Count > 0)
            {
                sb.Append('{');
                Serialize(new StringWriter(sb, CultureInfo.InvariantCulture), settings);
                sb.Append('}');
            }
            return sb.ToString();
        }

        /// <summary>
        /// Serializes a single value into JSON format.
        /// </summary>
        /// <param name="value">The value to serialize.</param>
        /// <returns>The JSON representation of the <paramref name="value"/>.</returns>
        public static string SerializeValue(object value)
        {
            StringBuilder sb = new StringBuilder();
            using (StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture))
            {
                Serialize(sw, value);
            }
            return sb.ToString();
        }
    }
}
